package carsharing;

/*
 * This class represents the state, and it's composed of the following information:
 *    - The size of the city
 *    - A vector with the people registered to the service (member). Each member has an attribute that indicates 
 *      if it's a driver or not
 *    - 
 *    
 */

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.Random;

import carsharing.Stop.StopType;


public class Carsharing {

	final public static int CITY_SIZE = 100;
	private int NDRIVERS;
	private final int MAX_PLACES = 2;
	private Vector<Member> members = new Vector<Member>();
	private Vector<Driver> drivers = new Vector<Driver>();
	
	public Carsharing(int numMembers, int ndrivers) {
		members.setSize(numMembers);
		NDRIVERS = ndrivers;
		if(ndrivers > numMembers)NDRIVERS = numMembers;
		drivers.setSize(NDRIVERS);
		
	}
	
	public Carsharing(Carsharing c) {
		this.NDRIVERS = c.NDRIVERS;
		this.members = new Vector<Member>();
		this.drivers = new Vector<Driver>();
		for(int i = 0; i < c.members.size(); ++i) {
			this.members.add(c.members.get(i).copy());
		}
		for(int i = 0; i < c.drivers.size(); ++i) {
			this.drivers.add(c.drivers.get(i).copy());
		}
	}
	
	public int getDist() {
		int dist = 0;
		for (int i = 0; i < drivers.size(); i++) {
			if (drivers.get(i).isAvailable()) {
				dist += drivers.get(i).getDist();
			}
		}
		return dist;
	}
	
	public void genRandomMembers(){
		
		// Assigns randomly coordinates to members
		Random r = new Random();
		for(int i = 0; i < members.size(); i++){
			Coordinates source, destination;
			source = new Coordinates(r.nextInt(CITY_SIZE), r.nextInt(CITY_SIZE));
			destination = new Coordinates(r.nextInt(CITY_SIZE), r.nextInt(CITY_SIZE));
			Member mem = new Member(i, source, destination);
			members.set(i, mem);
		}
		for(int i = 0; i < NDRIVERS; i++) {
			members.get(i).setDriver(true);
			members.get(i).setCarried(true);
			Driver driv = new Driver(i, members.get(i).getSource(),members.get(i).getDestination());
			drivers.set(i, driv);
		}
		this.printMembers();
	}
	
	public boolean isDriver(int memberId) {
		return members.get(memberId).isDriver();
	}
	
	public void printMembers() {
		for(int i = 0; i < members.size(); i++){
			members.get(i).print();
			System.out.println();
		}
	}

	public void printDrivers() {
		for(int i = 0; i < drivers.size(); i++){
			if(drivers.get(i).isAvailable()) {
				drivers.get(i).printStops();
				System.out.println();
			}
		}
	}
	
	public int getNumMembers() {
		return members.size();
	}
	
	public int getNumDrivers() {
		return drivers.size();
	}
	public int getNumAvailableDrivers() {
		int x = 0;
		for(int i = 0; i < drivers.size(); i++){
			if(drivers.get(i).isAvailable()) {
				++x;
			}
		}
		return x;
	}
	
	public int getNumReducibleDrivers() {
		int x = 0;
		for(int i = 0; i < drivers.size(); i++){
			if(canReductDriver(i) != -1) {
				++x;
			}
		}
		return x;
	}
	
	public void calculateDriversDist() {
		for(int i = 0; i < drivers.size(); i++) {
			drivers.get(i).calculateDist();
		}
	}
	public int getDriverDist(int driverId) {
		if(!drivers.get(driverId).isAvailable()) return 0;
		return drivers.get(driverId).getDist();
	}
	
	public boolean isDriverAvailable(int driverId) {
		return drivers.get(driverId).isAvailable();
	}


	
	
	
	//*******************************************************************************************************
	//*******************************************INITIAL SOLUTIONS*******************************************
	//*******************************************************************************************************
	

		//Initial solution one - Assign the passengers completely randomly
	public int solIni1() {
		genRandomMembers();
		int dist = 0;
		for (int i = 0; i < drivers.size(); i++) {
			Driver aux = drivers.get(i);
			aux.addInitialStop(new Stop(StopType.PICK, aux.getSource(), aux.getMemberId()));
			aux.addInitialStop(new Stop(StopType.PICK, members.get(i).getSource(), members.get(i).getId()));
			aux.addInitialStop(new Stop(StopType.PICK, members.get(i+1).getSource(), members.get(i+1).getId()));
			aux.addInitialStop(new Stop(StopType.DROP, members.get(i).getDestination(), members.get(i).getId()));
			aux.addInitialStop(new Stop(StopType.DROP, members.get(i+1).getDestination(), members.get(i+1).getId()));
			aux.addInitialStop(new Stop(StopType.PICK, aux.getDestination(), aux.getMemberId()));
			aux.calculateDist();
			dist += aux.getDist();
		}
		return dist;
	}
	
	
	//Initial solution two - We assign the two nearest members to each driver.
	public int solIni2() {
		genRandomMembers();
		Coordinates driver;
		Member memb;
		int distTotal, distMin1, distMin2, distAux;
		int mem1, mem2;
		distTotal = 0;
		for(int i = 0; i < drivers.size(); i++){
			distMin1 = distMin2 = CITY_SIZE * 2;
			mem1 = mem2 = -1;
			driver = drivers.get(i).getSource();
			for(int j = 0; j < members.size(); j++){
				memb = members.get(j);
				if(!memb.isDriver() && !memb.isCarried()){
					distAux = driver.distance(memb.getSource());
					if(distAux < distMin1){
						distMin2 = distMin1;
						mem2 = mem1;
						distMin1 = distAux;
						mem1 = memb.getId();
					}
					else if(distAux < distMin2){
						distMin2 = distAux;
						mem2 = memb.getId();
					}
				}
			}
			
			//We have to save the stops on the list
			Driver aux = drivers.get(i);
			aux.addInitialStop(new Stop(StopType.PICK, aux.getSource(), aux.getMemberId()));
			if(mem1 != -1) {
				aux.setCarrying(aux.getCarrying()+1);
				aux.addInitialStop(new Stop(StopType.PICK, members.get(mem1).getSource(), mem1));
				members.get(mem1).setCarried(true);
				members.get(mem1).setCarriedBy(i);
				if(mem2 != -1) {
					aux.setCarrying(aux.getCarrying()+1);
					aux.addInitialStop(new Stop(StopType.PICK, members.get(mem2).getSource(), mem2));
					members.get(mem2).setCarried(true);
					members.get(mem2).setCarriedBy(i);
					
					// We check if is better to drop first the mem1 or the mem2
					distMin1 = members.get(mem2).getSource().distance(members.get(mem1).getDestination()) + 
							members.get(mem1).getDestination().distance(members.get(mem2).getDestination()) + 
							members.get(mem2).getDestination().distance(aux.getDestination());
					distMin2 = members.get(mem2).getSource().distance(members.get(mem2).getDestination()) + 
							members.get(mem2).getDestination().distance(members.get(mem1).getDestination()) + 
							members.get(mem1).getDestination().distance(aux.getDestination());
					if(distMin1 < distMin2) {
						aux.addInitialStop(new Stop(StopType.DROP, members.get(mem1).getDestination(), mem1));
						aux.addInitialStop(new Stop(StopType.DROP, members.get(mem2).getDestination(), mem2));
					}
					else {
						aux.addInitialStop(new Stop(StopType.DROP, members.get(mem2).getDestination(), mem2));
						aux.addInitialStop(new Stop(StopType.DROP, members.get(mem1).getDestination(), mem1));
					}
				}
				else aux.addInitialStop(new Stop(StopType.DROP, members.get(mem1).getDestination(), mem1));
			}
			aux.addInitialStop(new Stop(StopType.DROP, aux.getDestination(), aux.getMemberId()));
			aux.calculateDist();
			distTotal += aux.getDist();
		}
		return distTotal;
	}
	
	
	// Initial solution three - Take the two members with the closest destination
	public int solIni3() {
		genRandomMembers();
		Coordinates driver;
		Member memb;
		int distTotal, distMin1, distMin2, distAux;
		int mem1, mem2;
		distTotal = 0;
		for(int i = 0; i < drivers.size(); i++){
			distMin1 = distMin2 = CITY_SIZE * 2;
			mem1 = mem2 = -1;
			driver = drivers.get(i).getDestination();
			for(int j = 0; j < members.size(); j++){
				memb = members.get(j);
				if(!memb.isDriver() && !memb.isCarried()){
					distAux = driver.distance(memb.getDestination());
					if(distAux < distMin1){
						distMin2 = distMin1;
						mem2 = mem1;
						distMin1 = distAux;
						mem1 = memb.getId();
					}
					else if(distAux < distMin2){
						distMin2 = distAux;
						mem2 = memb.getId();
					}
				}
			}
			
			//We have to save the stops on the list
			Driver aux = drivers.get(i);
			aux.addInitialStop(new Stop(StopType.PICK, aux.getSource(), aux.getMemberId()));
			if(mem1 != -1) {
				aux.setCarrying(aux.getCarrying()+1);
				aux.addInitialStop(new Stop(StopType.PICK, members.get(mem1).getSource(), mem1));
				members.get(mem1).setCarried(true);
				members.get(mem1).setCarriedBy(i);
				if(mem2 != -1) {
					aux.setCarrying(aux.getCarrying()+1);
					aux.addInitialStop(new Stop(StopType.PICK, members.get(mem2).getSource(), mem2));
					members.get(mem2).setCarried(true);
					members.get(mem2).setCarriedBy(i);
					
					// We check if is better to drop first the mem1 or the mem2
					distMin1 = members.get(mem2).getSource().distance(members.get(mem1).getDestination()) + 
							members.get(mem1).getDestination().distance(members.get(mem2).getDestination()) + 
							members.get(mem2).getDestination().distance(aux.getDestination());
					distMin2 = members.get(mem2).getSource().distance(members.get(mem2).getDestination()) + 
							members.get(mem2).getDestination().distance(members.get(mem1).getDestination()) + 
							members.get(mem1).getDestination().distance(aux.getDestination());
					if(distMin1 < distMin2) {
						aux.addInitialStop(new Stop(StopType.DROP, members.get(mem1).getDestination(), mem1));
						aux.addInitialStop(new Stop(StopType.DROP, members.get(mem2).getDestination(), mem2));
					}
					else {
						aux.addInitialStop(new Stop(StopType.DROP, members.get(mem2).getDestination(), mem2));
						aux.addInitialStop(new Stop(StopType.DROP, members.get(mem1).getDestination(), mem1));
					}
				}
				else aux.addInitialStop(new Stop(StopType.DROP, members.get(mem1).getDestination(), mem1));
			}
			aux.addInitialStop(new Stop(StopType.DROP, aux.getDestination(), aux.getMemberId()));
			aux.calculateDist();
			distTotal += aux.getDist();
		}
		return distTotal;
	}
	
	
	
	//*******************************************************************************************************
	//*******************************************OPERATORS***************************************************
	//*******************************************************************************************************
	
	//Operator 1 - Assign Members: Just assign the member memId to the driver driverId, deallocating member if he was assigned to a driver
	public int reAssignMember(int memId, int driverId) {
		int oldDriver = -1;
		Member m = members.get(memId);
		Driver d = drivers.get(driverId);
		if(m.isCarried()) oldDriver = deallocate(memId);
		m.setCarried(true);
		m.setCarriedBy(driverId);
		d.addStop(new Stop(StopType.PICK, m.getSource(), memId));
		d.addStop(new Stop(StopType.DROP, m.getDestination(), memId));
		minPath(driverId);
		if(oldDriver != -1) minPath(oldDriver);
		return oldDriver;
	}
	
	
	//OPERATOR 2 - Drivers Reduction: A driver turns into NO driver (and all its passengers
	//are spread among the other drivers (to the first driver with space in his/her car)
	public boolean driverReduction(int driverId, int newDriver) {
		int memberId = canReductDriver(driverId);
		if(memberId != -1) {
			members.get(memberId).setCarried(false);
			members.get(memberId).setDriver(false);
			drivers.get(driverId).setAvailable(false);
			reAssignMember(memberId, newDriver);
			return true;
		}
		return false;
	}
	
	
	//*******************************************************************************************************
	//*******************************************AUXILIAR FUNCTIONS******************************************
	//*******************************************************************************************************
	
	
	public void minPath(int driverId) {
		Driver driver = this.drivers.get(driverId);
		if(driver.nStops() <= 4) return;
		Driver minDist = driver.copy();
		minDist.calculateDist();
		minDist = recMinPath(driver, minDist, 1);
		//System.out.println();
		this.drivers.remove(driverId);
		this.drivers.add(driverId, minDist);
	}
	
	private Driver recMinPath(Driver driver, Driver minDriver, int x) {
		if(x == driver.nStops() - 2) {
			if(driver.checkStops()) {
				driver.calculateDist();
				if(driver.getDist() < minDriver.getDist()) {
					minDriver = driver.copy();
					return minDriver;
				}
			}
		}
		else {
			for(int i = x; i < driver.nStops() - 1; ++i) {
				driver.swapStops1(x, i);
				if(driver.preCheckStops(x)) {
					minDriver = recMinPath(driver, minDriver, x+1);
				}
				driver.swapStops1(x, i);
			}
		}
		return minDriver;
	}
	
	
	public ArrayList<Carsharing> permutStops(int driver) {
		ArrayList<Carsharing> CarsharingList = new ArrayList<Carsharing>();
		Carsharing aux;
		int stepSize;
		aux = new Carsharing(this);
		CarsharingList.add(aux);

		for(int i = 0; i < (drivers.get(driver).nStops() - 2) - 1; i++) {
			stepSize = CarsharingList.size();
			for(int j = i + 1; j < (drivers.get(driver).nStops() - 2); j++) {
				if(i == 0) {
					aux = new Carsharing(this);
					aux.drivers.get(driver).swapStops(i, j);
					if(aux.drivers.get(driver).checkStops()) {
						aux.calculateDriversDist();
						CarsharingList.add(aux);
					}
				}
				else {
					for(int k = 0; k < stepSize; k++) {
						aux = new Carsharing(CarsharingList.get(k));
						aux.drivers.get(driver).swapStops(i, j);
						if(aux.drivers.get(driver).checkStops()) {
							aux.calculateDriversDist();
							CarsharingList.add(aux);
						}
					}
				}
			}
		}
		return CarsharingList;
	}
	
	// Support function for reAssignMembers - Deallocate: Deallocate the member with id memId
	public int deallocate(int memId) {
		Member m = members.get(memId);
		int driverId = m.getCarriedBy();
		Driver d = drivers.get(driverId);
		m.setCarriedBy(-1);
		m.setCarried(false);
		d.delStops(memId);
		return driverId;
	}
	
	public int canReductDriver(int driverId) {
		//We can apply if any driver have not any passenger assigned
		if(drivers.get(driverId).isAvailable() && drivers.get(driverId).nStops() == 2) return drivers.get(driverId).getMemberId();
		else return -1;
	}

}
